59. 协方差分析

本章概览:为什么要研究变量间的关系?

协方差(Covariance)和相关系数(Correlation)是衡量两个变量关系的基础统计量。

在金融领域,这些指标帮助我们:

  • 量化风险:投资组合的风险取决于资产间的协方差
  • 分散化:选择低相关资产降低组合风险
  • 因子识别:发现驱动资产收益的共同因子
  • 风险对冲:寻找负相关资产进行对冲

数学基础:协方差的定义

总体协方差

\[ \text{Cov}(X, Y) = E[(X - \mu_X)(Y - \mu_Y)] \]

样本协方差

\[ \text{Cov}(X, Y) = \frac{1}{n-1}\sum_{i=1}^n (x_i - \bar{x})(y_i - \bar{y}) \]

协方差的基本性质

  • \(\text{Cov}(X, X) = \text{Var}(X)\)(自身的协方差等于方差)
  • \(\text{Cov}(X, Y) = \text{Cov}(Y, X)\)(对称性)
  • \(\text{Cov}(aX, bY) = ab \cdot \text{Cov}(X, Y)\)(双线性)

直觉理解:协方差衡量两个变量的「同步偏离」程度

  • 正值 → 同向变动
  • 负值 → 反向变动
  • 零 → 无线性关联

数学基础:Pearson 相关系数

\[ \rho_{XY} = \frac{\text{Cov}(X, Y)}{\sigma_X \sigma_Y} = \frac{\sum(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum(x_i - \bar{x})^2}\sqrt{\sum(y_i - \bar{y})^2}} \]

核心性质

  • \(-1 \leq \rho \leq 1\)
  • \(\rho = 1\):完全正相关
  • \(\rho = -1\):完全负相关
  • \(\rho = 0\):无线性相关(可能有非线性关系)

协方差 vs 相关系数:对比

特性 协方差 相关系数
量纲 有单位(如%²) 无量纲
范围 \((-\infty, +\infty)\) \([-1, 1]\)
标准化
可比性 不同资产对不可比 所有资产对可比
应用 计算组合方差 衡量关系强度

⭐ 平台任务1:读取销售数据

以下代码与教学平台任务要求完全一致:

Listing 1
# 注:sale_points.csv数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
sale_points-pd.read_csv('sale_points.csv')  # 从CSV文件读取数据
sale points.head()  # 查看sale points前5行数据

⭐ 平台任务2:相关分析与协方差分析

以下代码与教学平台任务要求完全一致:

Listing 2
# 注:相关分析sales.csv数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
import pandas as pd
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
import numpy as np  # 导入NumPy数值计算库
# 读取数据
data = pd.read_csv('相关分析sales.csv')
# 计算皮尔逊相关系数
corr =
# 计算相关性矩阵
corr_matrix = data.corr()
# 绘制热力图
fig, ax = plt.subplots()
im = ax.imshow(corr_matrix, cmap='YlGnBu')  # 显示热力图矩阵
# 显示相关系数的值
for i in range(corr_matrix.shape[0]):
    for j in range(corr_matrix.shape[1]):  # 遍历range(corr_matrix.shape[1])中的每个j
                text = ax.text(j, i, round(corr_matrix.iloc[i, j],2),  # 按位置索引提取显示相关系数的值
                       ha="center", va="center", color="black")  # 设置标注文本的水平和垂直对齐方式
# 设置坐标轴标签和标题
ax.set_xticks(np.arange(len(corr_matrix.columns)))
ax.set_yticks(np.arange(len(corr_matrix.columns)))  # 设置Y轴刻度标签
ax.set_xticklabels(corr_matrix.columns)  # 设置X轴刻度标签
ax.set_yticklabels(corr_matrix.columns)  # 设置Y轴刻度标签
ax.set_title("Correlation Heatmap")  # 设置图表标题
# 在图形旁添加颜色条
cbar = ax.figure.colorbar(im, ax=ax)
# 显示图形
plt.savefig("热力图.png")
# 输出结果
print('Pearson correlation coefficient: ', corr)
if abs(corr) >= 0.7:  # 条件判断:abs(corr) >= 0.7
    print('存在显著的线性相关性')  # 输出存在显著的线性相关性
else:  # 不满足以上条件时
    print('不存在显著的线性相关性')  # 输出不存在显著的线性相关性
    

#任务二
import numpy as np
from scipy import stats  # 导入SciPy科学计算库
import pandas as pd  # 导入Pandas数据分析库
import statsmodels.api as sm  # 导入统计建模库
import statsmodels.formula.api as smf  # 导入统计建模库
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
from statsmodels.stats.multicomp import pairwise_tukeyhsd  # 导入统计建模库
from statsmodels.graphics.api import interaction_plot  # 导入统计建模库
from matplotlib.font_manager import FontProperties  # 导入Matplotlib库
myfont=  # 设置中文字体属性
 
# 从CSV文件读取数据存入sale_points
sale_points = pd.read_csv(u'https://huoran.oss-cn-shenzhen.aliyuncs.com/20221116/csv/1592817699758039040.csv',encoding = "gbk")
 
sale_points['market'] = sale_points['market'].astype('category')  # 转换数据类型
# 定义列表sale_points['market'].cat.categories
sale_points['market'].cat.categories=['market 1', 'market 2', 'market 3']
 
sale_points['warranty'] = sale_points['warranty'].astype('category')  # 转换数据类型
# 定义列表sale_points['warranty'].cat.categories
sale_points['warranty'].cat.categories=['1 years', '3 years']
 
print(sale_points.head())  # 输出前几行数据
 
formula = 'sales ~ points + C(market) * C(warranty)'  # 定义模型公式:sales ~ points + C(market) * C(warranty)
sale_points_anova_cov_est = smf.ols(formula, data = sale_points).fit() # dc_sales_est 是一个模型对象
print(sale_points_anova_cov_est.summary())  # 输出合计值

实战:协方差矩阵计算

Listing 3
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 创建多资产收益率数据(模拟500个交易日)
np.random.seed(42)
n_days = 500

# 各资产的期望日收益率
mean_returns = [0.001, 0.0008, 0.0012, 0.0005]

# 设定真实的协方差矩阵
cov_matrix_true = np.array([
    [0.0004, 0.0002, 0.00015, 0.0001],
    [0.0002, 0.0003, 0.0001, 0.00005],
    [0.00015, 0.0001, 0.00035, 0.00008],
    [0.0001, 0.00005, 0.00008, 0.0002]
])

# 生成多元正态分布数据
returns = np.random.multivariate_normal(mean_returns, cov_matrix_true, n_days)

# 转换为DataFrame
assets = ['贵州茅台', '五粮液', '招商银行', '中国平安']
df_returns = pd.DataFrame(returns, columns=assets)

print('收益率数据(前10行):')
print(df_returns.head(10))
收益率数据(前10行):
       贵州茅台       五粮液      招商银行      中国平安
0 -0.021316  0.007675 -0.001451 -0.000617
1 -0.006528  0.016661  0.013422 -0.009431
2  0.016949  0.004799 -0.001382  0.004445
3 -0.004128 -0.029601  0.015739  0.019084
4  0.033404  0.001141  0.007167  0.008088
5 -0.016207 -0.029359 -0.014025 -0.017959
6  0.012169  0.004621  0.001933  0.018252
7 -0.001172  0.015955  0.009460  0.022482
8  0.001602 -0.010140  0.018858 -0.012143
9 -0.010412 -0.021935  0.018074  0.019635

计算协方差矩阵与相关系数矩阵

Listing 4
# 计算协方差矩阵
cov_matrix = df_returns.cov()
print('协方差矩阵:')
print(cov_matrix.round(6))

# 计算Pearson相关系数矩阵
corr_matrix = df_returns.corr()
print('\n相关系数矩阵:')
print(corr_matrix.round(4))
协方差矩阵:
          贵州茅台       五粮液      招商银行      中国平安
贵州茅台  0.000361  0.000171  0.000115  0.000072
五粮液   0.000171  0.000289  0.000077  0.000033
招商银行  0.000115  0.000077  0.000329  0.000055
中国平安  0.000072  0.000033  0.000055  0.000179

相关系数矩阵:
        贵州茅台     五粮液    招商银行    中国平安
贵州茅台  1.0000  0.5276  0.3322  0.2823
五粮液   0.5276  1.0000  0.2494  0.1438
招商银行  0.3322  0.2494  1.0000  0.2283
中国平安  0.2823  0.1438  0.2283  1.0000

相关性可视化:热力图

plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix,
            annot=True, fmt='.2f',
            cmap='RdYlGn', center=0,
            square=True, linewidths=0.5,
            cbar_kws={'label': '相关系数'},
            vmin=-1, vmax=1)
plt.title('资产收益率相关系数矩阵', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()
Figure 1: 相关系数矩阵热力图

找出相关性最高/最低的资产组合

Listing 5
# 提取上三角矩阵(排除对角线),避免重复
mask = np.triu(np.ones_like(corr_matrix, dtype=bool), k=1)
corr_upper = corr_matrix.where(mask)

# 找最大和最小相关系数
max_corr = corr_upper.max().max()
min_corr = corr_upper.min().min()
max_pair = corr_upper.stack().idxmax()
min_pair = corr_upper.stack().idxmin()

print(f'最高相关性: {max_pair} = {max_corr:.4f}')
print(f'最低相关性: {min_pair} = {min_corr:.4f}')
最高相关性: ('贵州茅台', '五粮液') = 0.5276
最低相关性: ('五粮液', '中国平安') = 0.1438

热力图配色方案选择

方案 特点 适用场景
RdYlGn 红-黄-绿,中心为0 一般相关性
coolwarm 蓝-白-红,中心为0 科学出版
viridis 紫-黄,感知均匀 色盲友好
RdBu 红-蓝,发散 正负对比明显

组合风险与协方差:投资组合优化

组合方差公式(矩阵形式):

\[ \sigma_p^2 = \mathbf{w}^T \Sigma \mathbf{w} \]

其中:

  • \(\mathbf{w}\):权重向量 \((n \times 1)\)
  • \(\Sigma\):协方差矩阵 \((n \times n)\)

分散化原理

当资产间相关系数 \(\rho_{ij} < 1\) 时:

\[ \sigma_p^2 < \sum_{i=1}^n w_i^2 \sigma_i^2 \]

含义:组合方差小于各资产方差的加权平均,实现风险分散

这正是投资组合理论的核心价值!

实战:生成随机投资组合

assets_list = ['贵州茅台', '五粮液', '招商银行', '中国平安']
n_assets = len(assets_list)

# 日协方差 → 年化协方差(×252个交易日)
cov_annual = cov_matrix * 252

# 生成1000个随机组合
np.random.seed(42)
n_portfolios = 1000

weights_list = []
returns_list = []
risks_list = []

for _ in range(n_portfolios):
    weights = np.random.random(n_assets)
    weights = weights / np.sum(weights)  # 归一化,权重和为1

    # 组合期望收益(年化)
    portfolio_return = np.sum(df_returns.mean() * weights) * 252

    # 组合方差 = w^T * Σ * w
    portfolio_variance = np.dot(weights.T, np.dot(cov_annual, weights))
    portfolio_std = np.sqrt(portfolio_variance)

    weights_list.append(weights)
    returns_list.append(portfolio_return)
    risks_list.append(portfolio_std)

df_portfolios = pd.DataFrame({
    '收益率': returns_list,
    '风险(标准差)': risks_list
})
Figure 2

可视化有效前沿

plt.figure(figsize=(12, 8))
scatter = plt.scatter(
    df_portfolios['风险(标准差)'], df_portfolios['收益率'],
    c=df_portfolios['收益率'] / df_portfolios['风险(标准差)'],
    cmap='viridis', alpha=0.6, s=50
)
plt.colorbar(scatter, label='夏普比率')

# 标记单个资产
for i, asset in enumerate(assets_list):
    asset_return = df_returns[asset].mean() * 252
    asset_risk = np.sqrt(cov_annual.iloc[i, i])
    plt.scatter(asset_risk, asset_return, s=200, marker='*',
                color='red', edgecolors='black', linewidths=1.5)
    plt.text(asset_risk, asset_return, f'  {asset}', fontsize=10)

plt.xlabel('年化风险(标准差)', fontsize=12)
plt.ylabel('年化收益率', fontsize=12)
plt.title('投资组合:风险与收益的关系', fontsize=16, fontweight='bold')
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
Figure 3: 投资组合:风险与收益的关系

最优组合:最大夏普比率

Listing 6
max_sharpe_idx = (df_portfolios['收益率'] / df_portfolios['风险(标准差)']).idxmax()
optimal_weights = weights_list[max_sharpe_idx]

print('最优组合权重(最大夏普比率):')
for asset, weight in zip(assets_list, optimal_weights):
    print(f'{asset}: {weight:.2%}')
最优组合权重(最大夏普比率):
贵州茅台: 1.73%
五粮液: 71.54%
招商银行: 8.59%
中国平安: 18.14%

时间序列相关性:滚动相关

# 计算贵州茅台和五粮液的60日滚动相关系数
window = 60
rolling_corr = df_returns['贵州茅台'].rolling(window).corr(df_returns['五粮液'])

plt.figure(figsize=(14, 6))
plt.plot(df_returns.index, rolling_corr, linewidth=2, color='#2E86AB')

# 参考线
plt.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
plt.axhline(y=rolling_corr.mean(), color='red', linestyle='--',
            linewidth=2, label=f'均值={rolling_corr.mean():.4f}')

# 填充正负相关区域
plt.fill_between(df_returns.index, rolling_corr, 0,
                 where=(rolling_corr >= 0), alpha=0.3, color='green', label='正相关期')
plt.fill_between(df_returns.index, rolling_corr, 0,
                 where=(rolling_corr < 0), alpha=0.3, color='red', label='负相关期')

plt.title('滚动相关系数(60日窗口)', fontsize=16, fontweight='bold')
plt.xlabel('日期', fontsize=12)
plt.ylabel('相关系数', fontsize=12)
plt.legend(fontsize=11, loc='upper left')
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
Figure 4: 滚动相关系数——相关性时变特征

滚动相关:统计分析

Listing 7
print('相关系数统计:')
print(f'均值: {rolling_corr.mean():.4f}')
print(f'标准差: {rolling_corr.std():.4f}')
print(f'最大值: {rolling_corr.max():.4f}')
print(f'最小值: {rolling_corr.min():.4f}')

# 识别相关性显著下降的时期
threshold = rolling_corr.mean() - 2 * rolling_corr.std()
low_corr_periods = rolling_corr[rolling_corr < threshold]
print(f'\n相关性异常低的时期({len(low_corr_periods)}天):')
print(low_corr_periods.head())
相关系数统计:
均值: 0.5228
标准差: 0.1118
最大值: 0.6752
最小值: 0.2269

相关性异常低的时期(28天):
397    0.289964
398    0.298138
399    0.298487
401    0.279462
402    0.275398
dtype: float64

滚动窗口的选择

窗口 天数 特点 适用场景
短期 20日 捕捉快速变化,噪声大 短线交易
中期 60日 平衡灵敏度和稳定性 一般分析
长期 120日 平滑,识别长期关系 战略配置

金融应用:行业相关性网络

industries_network = ['金融', '科技', '消费', '医药', '能源', '地产']
n_industries = len(industries_network)

# 构建行业相关系数矩阵
industry_corr = pd.DataFrame(
    [[1.00, 0.30, 0.35, 0.25, 0.40, 0.60],
     [0.30, 1.00, 0.55, 0.30, 0.25, 0.20],
     [0.35, 0.55, 1.00, 0.35, 0.20, 0.25],
     [0.25, 0.30, 0.35, 1.00, 0.15, 0.20],
     [0.40, 0.25, 0.20, 0.15, 1.00, 0.35],
     [0.60, 0.20, 0.25, 0.20, 0.35, 1.00]],
    index=industries_network, columns=industries_network
)

print('行业相关系数矩阵:')
print(industry_corr)
行业相关系数矩阵:
      金融    科技    消费    医药    能源    地产
金融  1.00  0.30  0.35  0.25  0.40  0.60
科技  0.30  1.00  0.55  0.30  0.25  0.20
消费  0.35  0.55  1.00  0.35  0.20  0.25
医药  0.25  0.30  0.35  1.00  0.15  0.20
能源  0.40  0.25  0.20  0.15  1.00  0.35
地产  0.60  0.20  0.25  0.20  0.35  1.00
Figure 5

行业相关性热力图

plt.figure(figsize=(10, 8))
mask = np.triu(np.ones_like(industry_corr, dtype=bool))
sns.heatmap(industry_corr,
            annot=True, fmt='.2f',
            cmap='RdYlGn', center=0,
            square=True, linewidths=0.5,
            mask=mask,
            vmin=-1, vmax=1)
plt.title('行业相关性结构', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
Figure 6: 行业相关性结构(下三角)

识别高度相关的行业对

Listing 8
high_corr_pairs = []
for i in range(n_industries):
    for j in range(i+1, n_industries):
        corr_val = industry_corr.iloc[i, j]
        if abs(corr_val) > 0.5:
            high_corr_pairs.append((industries_network[i], industries_network[j], corr_val))

print('高度相关行业对(|ρ| > 0.5):')
for pair in sorted(high_corr_pairs, key=lambda x: abs(x[2]), reverse=True):
    print(f'{pair[0]} - {pair[1]}: {pair[2]:.2f}')
高度相关行业对(|ρ| > 0.5):
金融 - 地产: 0.60
科技 - 消费: 0.55

行业轮动策略启示

基于相关性的行业轮动原则:

  • 高相关行业:同涨同跌,避免过度配置
  • 低相关行业:分散化效果好
  • 相关性变化:可预判板块轮动时机

实例:金融-地产相关系数0.60(同属周期板块),科技-消费相关系数0.55(消费升级主题)

偏相关系数:控制第三方变量

偏相关系数 \(\rho_{XY|Z}\):控制变量 \(Z\) 之后,\(X\)\(Y\) 的真实相关性。

金融应用

  • Alpha分离:剔除市场因子后的特质收益
  • 因子正交化:构建正交因子
  • 归因分析:分离不同来源的影响

实战:偏相关系数计算

Listing 9
from scipy.stats import pearsonr
from sklearn.linear_model import LinearRegression

np.random.seed(42)
n = 500

# 构造三变量数据
market = np.random.normal(0.001, 0.02, n)
industry_factor = np.random.normal(0, 0.01, n)
stock = 0.8 * market + 0.5 * industry_factor + np.random.normal(0, 0.015, n)

df_partial = pd.DataFrame({
    '市场': market,
    '个股': stock,
    '行业因子': industry_factor
})

# 简单相关系数
corr_market_stock = df_partial['市场'].corr(df_partial['个股'])
corr_industry_stock = df_partial['行业因子'].corr(df_partial['个股'])

print('简单相关系数:')
print(f'市场-个股: {corr_market_stock:.4f}')
print(f'行业因子-个股: {corr_industry_stock:.4f}')
简单相关系数:
市场-个股: 0.6652
行业因子-个股: 0.2234

偏相关系数:回归残差法

Listing 10
# 个股对行业因子回归,取残差
model_stock = LinearRegression().fit(
    df_partial[['行业因子']], df_partial['个股']
)
residual_stock = df_partial['个股'] - model_stock.predict(df_partial[['行业因子']])

# 市场对行业因子回归,取残差
model_market = LinearRegression().fit(
    df_partial[['行业因子']], df_partial['市场']
)
residual_market = df_partial['市场'] - model_market.predict(df_partial[['行业因子']])

# 偏相关系数 = 残差的相关系数
partial_corr = np.corrcoef(residual_market, residual_stock)[0, 1]

print(f'偏相关系数(控制行业因子):')
print(f'市场-个股: {partial_corr:.4f}')
print(f'解释: 剔除行业影响后,市场与个股的真实相关性')
偏相关系数(控制行业因子):
市场-个股: 0.7018
解释: 剔除行业影响后,市场与个股的真实相关性

本章小结

  • 协方差:衡量两变量同步偏离程度,有量纲
  • 相关系数:标准化后的协方差,范围 \([-1, 1]\)
  • 热力图:快速识别多资产相关性模式
  • 组合风险\(\sigma_p^2 = \mathbf{w}^T \Sigma \mathbf{w}\),协方差决定分散化效果
  • 滚动相关:捕捉相关性的时变特征
  • 偏相关:控制第三方变量后的真实关系